import com.sun.jdi.Value;
import org.w3c.dom.ranges.Range;

import java.io.*;
import java.util.*;



class Method
{
    String packageName;
    Id recvName;
    boolean isPtr;
    String name;
    Id funcId;
    FuncType funcType;
}

class Function
{
    String name;
    Id id;
    List<Statement> statements;
    int localArea;
    int argsArea;
    List<Variable> localVars;
    List<Variable> params;
    List<Variable> results;
    FuncType funcType;
    FuncDecl decl;
    Method method;
    String packageName;
    SymbolTable symbolTable;
}

class Variable
{
    enum VType{Var, Tmp, Const, Lit, Nil};
    String name;
    String packageName;
    boolean isGlobal;
    String globalId;
    int offset;
    boolean isConst;
    boolean isTmp;
    Object type;
    VType vtype;
}

class ForeignFunc
{
    String symbol;
    FuncDecl funcDecl;
}

class MetaForRange
{
    boolean isMap;
    Variable lenVar;
    Variable indexVar;
    Variable mapVar;
    Variable itemVar;
}

class MetaFor
{
    MetaFor out;
    MetaForRange range;
}

class MetaBranch
{
    MetaFor meta;
}

class ValueIr
{
    Object value;
    ValueType type;
    enum ValueType{Var, Quad, Block, Func, Package, Int};
}

enum OPIr
{
    AddIr,           // +
    SubIr,           // -
    NotIr,           // !
    MulIr,           // *
    DivIr,           // /
    RemIr,           // %
    ShlIr,           // <<
    ShrIr,           // >>
    AndIr,           // &
    AndNotIr,        // &^
    OrIr,            // |
    XorIr,           // ^
    EqlIR,           // ==
    NeqIr,           // !=
    LssIr,           // <
    LeqIR,           // <=
    GtrIr,           // >
    GeqIr,           // >=
    AndAndIr,        // &&
    OrOrIr,          // ||
    SelIr,           // x.sel 实际上没有操作
    ParamIr,         // 压入参数
    CallIr,          // 调用函数
    GetCallIr,       // 获得调用结果
    RetIr,           // 函数返回值
    AssignIr,        // res=arg1
    IfTrueIr,        // if (true) (block)
    IfFalseIr,       // if (false) (block)
    IndexIr,         // x[index] get addr
    StarIr,          //
    GoToIr,          // goto (block)
    AddrIr,          // &x  res = &arg1
    AllocRetIr,      // alloc stack for ret
    LastRetIr       // ret leave
}

class QuadIr
{
    int id;
    ValueIr res;
    ValueIr arg1;
    ValueIr arg2;
    OPIr op;
    QuadIr next;
    QuadIr prev;
    BlockIr belongBlock;
    QuadIr(){}
    QuadIr(ValueIr res, ValueIr arg1, ValueIr arg2, OPIr op)
    {
        this.res = res;
        this.arg1 = arg1;
        this.arg2 = arg2;
        this.op = op;
    }
}

class BlockIr
{
    int blockId;
    QuadIr first;
    QuadIr last;
    FuncIr belongFunc;
    BlockIr next;
    BlockIr prev;
}

class FuncIr
{
    PackageIr belongPackage;
    Function func;
    BlockIr block;
    QuadIr retAddr;
}

class PackageIr
{
    String name;
    List<ValueDecl> values = new ArrayList<>();
    List<TypeDecl> types = new ArrayList<>();
    List<FuncIr> funcIrs = new ArrayList<>();
    List<Function> functions = new ArrayList<>();
    List<StringContainer> strings = new ArrayList<>();
}

public class IrGenerator
{
    public List<PackageManager> packages;
    public Map<String, Id> exportedIds;
    public Map<Object, Integer> offsets;
    public Map<Object, Function> returnMeta = new HashMap<>();
    public Map<Object, MetaFor> forMeta = new HashMap<>();
    public Map<Object, MetaBranch> branchMeta = new HashMap<>();
    public Map<Object, FuncIr> functionIrMeta = new HashMap<>();
    public Map<Object, PackageIr> packageIrMeta = new HashMap<>();
    public List<TypeDecl> types;
    public Map<Object, Map<String, Method>> recvMethods = new HashMap<>();
    public List<PackageIr> packageIrs = new ArrayList<>();
    public List<FuncIr> funcIrs = new ArrayList<>();
    Scope globalScope;
    PackageManager currentPackage;
    Function mainFunction;
    Function currentFunction;
    MetaFor currentFor;
    FuncIr currentFunctionIr;
    BlockIr currentBlock;
    QuadIr currentQuad;
    int blockId = 0;
    int quadId = 0;

    IrGenerator()
    {
        packages = new ArrayList<>();
        exportedIds = new HashMap<>();
        offsets = new HashMap<>();
        types = new ArrayList<>();
        setGlobalScope();
    }

    IrGenerator(String mainDir)
    {
        packages = new ArrayList<>();
        exportedIds = new HashMap<>();
        offsets = new HashMap<>();
        types = new ArrayList<>();
        setGlobalScope();
        PackageManager mainPackage = new PackageManager(mainDir);
        packages.add(mainPackage);
        Set<String> dependency = new HashSet<>();
        try
        {
            dependency = mainPackage.resolveDependency(dependency);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        for(String dir : dependency)
        {
            String path = PackageManager.getDenpendencyPath(dir);
            PackageManager packageManager = new PackageManager(path);
            packages.add(packageManager);
        }
    }

    public void generateIr()
    {
        for(PackageManager packageManager : packages)
        {
            PackageIr packageIr = new PackageIr();
            currentPackage = packageManager;
            packageIr.name = packageManager.name.name;
            packageIr.functions = packageManager.functions;
            packageIr.types = packageManager.types;
            packageIr.values = packageManager.vars;
            packageIr.strings = packageManager.strings;
            packageIrs.add(packageIr);

            for(Function function : packageIr.functions)
            {
                FuncIr funcIr = new FuncIr();
                funcIr.belongPackage = packageIr;
                funcIr.func = function;
                currentFunctionIr = funcIr;
                functionIrMeta.put(function.id, funcIr);
                packageIr.funcIrs.add(funcIr);
            }

            for(Id packageId : packageManager.packageIds)
            {
                packageIrMeta.put(packageId, packageIr);
            }
        }
    }

    public Variable newLitVariable(int size, Object type)
    {
        Variable variable = new Variable();
        variable.type = type;
        variable.offset = size;
        variable.isTmp = true;
        variable.vtype = Variable.VType.Lit;
        BasicLit basicLit = (BasicLit)type;
        variable.name = "Lit" + basicLit.value;
        return variable;
    }

    public void changeBlock(BlockIr block)
    {
        currentBlock = block;
        currentQuad = currentBlock.last;
    }

    public ValueIr newBlockValue(BlockIr blockIr)
    {
        ValueIr valueIr = new ValueIr();
        valueIr.value = blockIr;
        valueIr.type = ValueIr.ValueType.Block;
        return valueIr;
    }
    public QuadIr newQuad(ValueIr res, ValueIr arg1, ValueIr arg2, OPIr op)
    {
        QuadIr quadIr = new QuadIr();
        quadIr.res = res;
        quadIr.arg1 = arg1;
        quadIr.arg2 = arg2;
        quadIr.op = op;
        quadIr.id = quadId;
        quadId++;
        quadIr.belongBlock = currentBlock;
        quadIr.prev = currentQuad;
        if(currentBlock != null && currentBlock.first == null)
            currentBlock.first = quadIr;
        if(currentBlock != null)
            currentBlock.last = quadIr;
        if(currentQuad != null)
            currentQuad.next = quadIr;
        currentQuad = quadIr;
        return quadIr;
    }

    public BlockIr newBlock()
    {
        BlockIr blockIr = new BlockIr();
        blockIr.blockId = blockId;
        blockId++;
        blockIr.belongFunc = currentFunctionIr;
        blockIr.prev = currentBlock;
        if(currentFunctionIr != null && currentFunctionIr.block == null)
            currentFunctionIr.block = blockIr;
        if(currentBlock != null)
        {
            if (currentBlock.next != null)
            {
                currentBlock.next.prev = blockIr;
                blockIr.next = currentBlock.next;
            }
            currentBlock.next = blockIr;
        }
        currentBlock = blockIr;
        return blockIr;
    }

    public void printIr() throws IOException, IllegalAccessException
    {
        FileOutputStream fileOutputStream = new FileOutputStream("ir.txt");
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "utf-8");
        for(int i=0; i<packageIrs.size(); i++)
        {
            currentPackage = packages.get(i);
            printIr.printSymbolTable(this, packageIrs.get(i).name, packages.get(i).symbolTable, outputStreamWriter);
            for(FuncIr funcIr : packageIrs.get(i).funcIrs)
            {
                printIr.printFunction(this, funcIr, outputStreamWriter);
            }
        }
        outputStreamWriter.flush();
        outputStreamWriter.close();
        fileOutputStream.close();
    }


    public void buildPackages() throws IOException, IllegalAccessException
    {
        for(int i=packages.size()-1; i>=0; i--)
        {
            packages.get(i).buildAst();
            packages.get(i).solveUnresolveSymbol(globalScope);
            walkPackage(packages.get(i));
        }
    }

    public void walkPackage(PackageManager pkg) throws IllegalAccessException
    {
        currentPackage = pkg;
        List<FuncDecl> funcs = new ArrayList<>();
        List<ValueDecl> vars = new ArrayList<>();
        List<ValueDecl> consts = new ArrayList<>();
        for(Decl decl : pkg.decls)
        {
            switch(decl.getClass().getName())
            {
                case "FuncDecl": funcs.add((FuncDecl) decl);break;
                case "TypeDecl": types.add((TypeDecl) decl);break;
                case "ValueDecl":
                    ValueDecl valueDecl = (ValueDecl) decl;
                    if(valueDecl.isConst)
                        consts.add(valueDecl);
                    else
                        vars.add(valueDecl);
            }
        }

        for(TypeDecl typeDecl : types)
        {
            ((ScopeObject)typeDecl.id.object).value = pkg.name;
            String kind = getKind(typeDecl.type);
            if(kind.equals("Struct"))
            {
                StructExpr type = (StructExpr)getRealType(typeDecl.type);
                assert type != null;
                calcStructOffset(type);
            }
            exportedIds.put(utils.getCombinedId(pkg.name, typeDecl.id), typeDecl.id);
            pkg.types.add(typeDecl);
            pkg.symbolTable.types.put(typeDecl.id, typeDecl.id.object);
        }

        for(FuncDecl funcDecl : funcs)
        {
            if(funcDecl.receiver.size() == 0)
                exportedIds.put(utils.getCombinedId(pkg.name, funcDecl.name), funcDecl.name);
            else
            {
                Method method = new Method();
                Field field = funcDecl.receiver.get(0);
                if(field.type.getClass().getName().equals("StarType"))
                {
                    method.isPtr = true;
                }
                method.packageName = pkg.name.name;
                method.recvName = funcDecl.receiver.get(0).id;
                method.name = funcDecl.name.name;
                method.funcType = funcDecl.type;
                method.funcId = funcDecl.name;
                registerMethod(method);
            }
        }

        for(ValueDecl valueDecl : consts)
        {
            for(Expr value : valueDecl.value)
                registerString(value);
            if(valueDecl.type == null)
            {
                List<Expr> types = new ArrayList<>();
                if(valueDecl.isMulti)
                {
                    for(Expr expr : valueDecl.value)
                    {
                        types.add((Expr) getType(expr));
                    }
                    valueDecl.types = types;
                }
                else
                    valueDecl.type = (Expr) getType(valueDecl.value.get(0));
            }

            for(int i=0; i<valueDecl.names.size(); i++)
            {
                ScopeObject scopeObject = (ScopeObject) valueDecl.names.get(i).object;
                if(valueDecl.isMulti)
                    scopeObject.value = newConst(pkg.name.name, valueDecl.names.get(i).name, valueDecl.types.get(i));
                else
                    scopeObject.value = newConst(pkg.name.name, valueDecl.names.get(i).name, valueDecl.type);
                exportedIds.put(utils.getCombinedId(pkg.name, valueDecl.names.get(i)), valueDecl.names.get(i));
                pkg.symbolTable.vars.put(valueDecl.names.get(i), valueDecl.names.get(i).object);
            }
            pkg.vars.add(valueDecl);
        }

        for(ValueDecl valueDecl : vars)
        {
            for(Expr value : valueDecl.value)
                registerString(value);
            if(valueDecl.type == null)
            {
                List<Expr> types = new ArrayList<>();
                if(valueDecl.isMulti)
                {
                    for(Expr expr : valueDecl.value)
                    {
                        types.add((Expr) getType(expr));
                    }
                    valueDecl.types = types;
                }
                else
                    valueDecl.type = (Expr) getType(valueDecl.value.get(0));
            }

            for(int i=0; i<valueDecl.names.size(); i++)
            {
                ScopeObject scopeObject = (ScopeObject) valueDecl.names.get(i).object;
                if(valueDecl.isMulti)
                    scopeObject.value = newVar(pkg.name.name, valueDecl.names.get(i).name, valueDecl.types.get(i));
                else
                    scopeObject.value = newVar(pkg.name.name, valueDecl.names.get(i).name, valueDecl.type);
                exportedIds.put(utils.getCombinedId(pkg.name, valueDecl.names.get(i)), valueDecl.names.get(i));
                pkg.symbolTable.vars.put(valueDecl.names.get(i), valueDecl.names.get(i).object);
            }
            pkg.vars.add(valueDecl);
        }

        for(FuncDecl funcDecl : funcs)
        {
            Function function = new Function();
            function.id = funcDecl.name;
            function.name = funcDecl.name.name;
            function.decl = funcDecl;
            function.funcType = funcDecl.type;
            function.argsArea = 16;
            function.packageName = currentPackage.name.name;
            function.symbolTable = new SymbolTable();
            function.params = new ArrayList<>();
            function.localVars = new ArrayList<>();
            function.statements = new ArrayList<>();
            function.results = new ArrayList<>();
            currentFunction = function;
            if(function.name.equals("main"))
                mainFunction = function;

            List<Field> params = new ArrayList<>();

            if(funcDecl.receiver.size() != 0)
                params.add(funcDecl.receiver.get(0));
            params.addAll(funcDecl.type.params);
            List<Field> results = new ArrayList<>(funcDecl.type.results);

            for(Field field : params)
            {
                ScopeObject object = (ScopeObject)field.id.object;
                object.value = registerParam(function, object.name, field.type);
                function.symbolTable.vars.put(field.id, object);
            }

            for(int i=0; i<results.size(); i++)
            {
                if(results.get(i).name == null)
                {
                    Variable variable = registerReturnValue(function, ".r" + String.valueOf(i), results.get(i).type);
                    ScopeObject scopeObject = new ScopeObject();
                    scopeObject.name = variable.name;
                    scopeObject.kind = ScopeObject.KIND.Var;
                    scopeObject.field = results.get(i);
                    scopeObject.value = variable;
                    Id id = new Id(variable.name);
                    id.object = scopeObject;
                    function.symbolTable.vars.put(id, scopeObject);
                }
                else
                {
                    Variable variable = registerReturnValue(function, results.get(i).id.name, results.get(i).type);
                    ScopeObject scopeObject = (ScopeObject)results.get(i).id.object;
                    scopeObject.value = variable;
                    function.symbolTable.vars.put(results.get(i).id, results.get(i).id.object);
                }
            }

            if(funcDecl.body != null)
            {
                function.statements = funcDecl.body.list;
                for(Statement statement : function.statements)
                {
                    registerStatement(statement);
                }

                if(funcDecl.receiver.size() != 0)
                {
                    function.method = newMethod(pkg.name.name, funcDecl);
                }
                pkg.functions.add(function);
            }
        }
    }

    private Method newMethod(String packageName, FuncDecl funcDecl)
    {
        Method method = new Method();
        Expr expr = funcDecl.receiver.get(0).type;
        if(expr instanceof StarType)
        {
            method.isPtr = true;
            expr = ((StarType) expr).type;
        }
        Id receiverType = (Id)expr;
        method.recvName = receiverType;
        method.packageName = packageName;
        method.name = funcDecl.name.name;
        method.funcType = funcDecl.type;
        method.funcId = funcDecl.name;
        return method;
    }

    private void registerStatement(Statement statement) throws IllegalAccessException
    {
        switch(statement.getClass().getName())
        {
            case "EmptyStatement":break;
            case "ExpressionStatement":
                ExpressionStatement expressionStatement = (ExpressionStatement) statement;
                registerString(expressionStatement.expr);
                break;
            case "DeclStatement":
                DeclStatement declStatement = (DeclStatement) statement;
                for(Decl decl : declStatement.decls)
                {
                    switch(decl.getClass().getName())
                    {
                        case "ValueDecl":
                            ValueDecl valueDecl = (ValueDecl) decl;
                            if(valueDecl.isMulti)
                            {
                                for(int i=0; i<valueDecl.value.size(); i++)
                                {
                                    Object type = getType(valueDecl.value.get(i));
                                    valueDecl.types.add((Expr) type);
                                    ScopeObject scopeObject = (ScopeObject) valueDecl.names.get(i).object;
                                    if(valueDecl.isConst)
                                        scopeObject.value = registerLocalValue(currentFunction, scopeObject.name, type, false);
                                    else
                                        scopeObject.value = registerLocalValue(currentFunction, scopeObject.name, type, true);
                                    currentFunction.symbolTable.vars.put(valueDecl.names.get(i), scopeObject);
                                }
                            }
                            else
                            {
                                if(valueDecl.types == null)
                                {
                                    if(valueDecl.value.size() == 0)
                                        System.out.println("value size unvalid");
                                    Object type = getType(valueDecl.value.get(0));
                                    valueDecl.type = (Expr) type;
                                }

                                for(int i=0; i<valueDecl.names.size(); i++)
                                {
                                    ScopeObject scopeObject = (ScopeObject)valueDecl.names.get(i).object;
                                    if(valueDecl.isConst)
                                        scopeObject.value = registerLocalValue(currentFunction, scopeObject.name, valueDecl.type, false);
                                    else
                                        scopeObject.value = registerLocalValue(currentFunction, scopeObject.name, valueDecl.type, true);
                                    currentFunction.symbolTable.vars.put(valueDecl.names.get(i), scopeObject);
                                }

                                for(Expr expr : valueDecl.value)
                                {
                                    registerString(expr);
                                }
                            }
                            break;

                    }
                }
                break;
            case "AssignStatement":
                AssignStatement assignStatement = (AssignStatement) statement;
                for(Expr expr : assignStatement.left)
                    registerString(expr);

                if(assignStatement.op == OP.Def)
                {
                    if(assignStatement.right.size() > 0)
                        registerString(assignStatement.right.get(0));
                    boolean isMulti = assignStatement.left.size() > 1 && assignStatement.right.size() == 1;
                    if(isMulti)
                    {
                        Expr expr = assignStatement.right.get(0);
                        List<Expr> exprs = new ArrayList<>();
                        switch(expr.getClass().getName())
                        {
                            case "CallExpr":
                                CallExpr callExpr = (CallExpr) expr;
                                exprs = utils.typeConversion(getReturnType(callExpr), Expr.class);
                                break;
                            case "TypeAssertExpr":
                                TypeAssertExpr typeAssertExpr = (TypeAssertExpr) expr;
                                Object type = getType(typeAssertExpr);
                                exprs.add((Expr) type);
                                exprs.add(new TBool());
                                break;
                            case "IndexExpr":
                                Object type1 = getType(expr);
                                exprs.add((Expr) type1);
                                exprs.add(new TBool());
                                break;
                        }

                        for(int i=0; i<assignStatement.left.size(); i++)
                        {
                            Id id = (Id)assignStatement.left.get(i);
                            ScopeObject scopeObject = (ScopeObject) id.object;
                            Expr right = assignStatement.right.get(i);
                            registerString(right);
                            Object type = getType(right);
                            scopeObject.value = registerLocalValue(currentFunction, id.name, type, true);
                        }
                    }
                    else
                    {
                        registerString(assignStatement.right.get(0));
                        for(Expr expr : assignStatement.left)
                        {
                            Id id = (Id)expr;
                            ScopeObject scopeObject = (ScopeObject) id.object;
                            Object type = getType(assignStatement.right.get(0));
                            scopeObject.value = registerLocalValue(currentFunction, id.name, type, true);
                        }
                    }
                }
                else
                {
                    for(Expr right : assignStatement.right)
                        registerString(right);
                }
                break;
            case "ReturnStatement":
                ReturnStatement returnStatement = (ReturnStatement)statement;
                for(Expr expr : returnStatement.results)
                    registerString(expr);
                returnMeta.put(statement, currentFunction);
                break;
            case "IFStatement":
                IFStatement ifStatement = (IFStatement) statement;
                if(ifStatement.init != null)
                {
                    registerStatement(ifStatement.init);
                }
                registerString(ifStatement.condition);
                if(ifStatement.elsePart != null)
                    registerStatement(ifStatement.elsePart);
                registerStatement(ifStatement.block);
                break;
            case "BlockStatement":
                BlockStatement blockStatement = (BlockStatement) statement;
                for(Statement statement1 : blockStatement.list)
                    registerStatement(statement1);
                break;
            case "ForStatement":
                ForStatement forStatement = (ForStatement) statement;
                MetaFor metaFor = new MetaFor();
                metaFor.out = currentFor;
                currentFor = metaFor;
                forMeta.put(forStatement, metaFor);
                if(forStatement.init != null)
                    registerStatement(forStatement.init);
                if(forStatement.cond != null)
                    registerString(forStatement.cond);
                if(forStatement.post != null)
                    registerStatement(forStatement.post);
                registerStatement(forStatement.body);
                currentFor = metaFor.out;
                break;
            case "RangeClause":
                RangeClause rangeClause = (RangeClause) statement;
                forMeta.put(rangeClause, currentFor);
                registerString(rangeClause.expr);
                Object collectionType = getRealType((Expr) getType(rangeClause.expr));
                Object keyType = getKeyTypeOfCollectionType((Expr) collectionType);
                Object elementType = getElementTypeOfCollectionType((Expr)collectionType);
                switch(getKind((Expr) collectionType))
                {
                    case "Slice": case "Array":
                        MetaForRange metaForRange = new MetaForRange();
                        metaForRange.lenVar = registerLocalValue(currentFunction, ".range.len",new TInt(), true);
                        metaForRange.indexVar = registerLocalValue(currentFunction, ".range.index", new TInt(), true);
                        currentFor.range = metaForRange;
                        break;
                    case "Map":
                        MetaForRange metaForRange1 = new MetaForRange();
                        metaForRange1.mapVar = registerLocalValue(currentFunction, ".range.map",new TInt(), true);
                        metaForRange1.itemVar = registerLocalValue(currentFunction, ".range.item", new TInt(), true);
                        currentFor.range = metaForRange1;
                        break;
                }

                if(rangeClause.define)
                {
                    if(rangeClause.left.size() > 1)
                    {
                        Id id = (Id) rangeClause.left.get(0);
                        ScopeObject scopeObject = (ScopeObject) id.object;
                        scopeObject.value = registerLocalValue(currentFunction,id.name,keyType, true);
                    }
                    if(rangeClause.left.size() > 2)
                    {
                        Id id = (Id) rangeClause.left.get(1);
                        ScopeObject scopeObject = (ScopeObject) id.object;
                        scopeObject.value = registerLocalValue(currentFunction,id.name,elementType, true);
                    }
                }
                break;
            case "IncDecStatement":
                registerString(((IncDecStatement)statement).x);
                break;
            case "TypeSwitchStatement":
                TypeSwitchStatement typeSwitchStatement = (TypeSwitchStatement) statement;
                if(typeSwitchStatement.init != null)
                    registerStatement(typeSwitchStatement.init);
                if(typeSwitchStatement.tag != null)
                    registerStatement(typeSwitchStatement.tag);
                for(Statement statement1 : typeSwitchStatement.body)
                    registerStatement(statement1);
                break;
            case "CaseStatement":
                CaseStatement caseStatement = (CaseStatement) statement;
                for(Expr expr : caseStatement.cases)
                    registerString(expr);
                for(Statement statement1 : caseStatement.body)
                    registerStatement(statement1);
                break;
            case "JumpStatement":
                JumpStatement jumpStatement = (JumpStatement) statement;
                MetaBranch metaBranch = new MetaBranch();
                metaBranch.meta = currentFor;
                branchMeta.put(jumpStatement, metaBranch);
                break;
        }
    }

    public Variable registerTmpVar(String name, Object type)
    {
        Variable variable = new Variable();
        variable.name = name;
        variable.isTmp = true;
        variable.type = type;
        variable.offset = getSizeOfType((Expr) type);
        variable.vtype = Variable.VType.Tmp;
        return variable;
    }

    public QuadIr newQuadWithTmpVar(ValueIr arg1, ValueIr arg2, OPIr op, Object type)
    {
        QuadIr quadIr = new QuadIr();
        quadIr.id = quadId;

        ValueIr res = new ValueIr();
        res.value = registerTmpVar("TmpVar" + String.valueOf(quadId), type);
        res.type = ValueIr.ValueType.Var;
        quadIr.res = res;
        quadIr.arg1 = arg1;
        quadIr.arg2 = arg2;
        quadIr.op = op;
        quadIr.prev = currentQuad;
        quadIr.belongBlock = currentBlock;
        if(currentBlock != null && currentBlock.first == null)
            currentBlock.first = quadIr;
        if(currentBlock != null)
            currentBlock.last = quadIr;
        if(currentQuad != null)
            currentQuad.next = quadIr;
        currentQuad = quadIr;
        quadId++;
        return quadIr;
    }

    public Method findMethod(Object type, Id methodName)
    {
        if(type.getClass().getName().equals("StarType"))
            type = ((StarType)type).type;
        ScopeObject scopeObject = null;
        switch (type.getClass().getName())
        {
            case "Id": scopeObject = (ScopeObject) ((Id)type).object;break;
            case "SelectorExpr":
                if(exportedIds.containsKey(utils.getQualifiedId((SelectorExpr) type)))
                    scopeObject = (ScopeObject) exportedIds.get(utils.getQualifiedId((SelectorExpr) type)).object;
                break;
        }
        if(recvMethods.containsKey(scopeObject))
        {
            return recvMethods.get(scopeObject).get(methodName.name);
        }
        return null;
    }

    public QuadIr newEmptyQuad(Object type)
    {
        Variable variable = new Variable();
        variable.name = "LitVariable";
        variable.isTmp = true;
        variable.offset = getSizeOfType((Expr) type);
        BasicLit basicLit = new BasicLit();
        basicLit.value = "0";
        basicLit.kind = LitKind.DecimalInt;
        variable.type = basicLit;
        variable.vtype = Variable.VType.Lit;
        ValueIr valueIr = new ValueIr();
        valueIr.value = variable;
        valueIr.type = ValueIr.ValueType.Var;
        return new QuadIr(valueIr, null, null, null);
    }

    public Variable newStructLocalVariable(Field field, Variable var)
    {
        Variable variable = new Variable();
        variable.name = var.name + "." + field.id.name;
        variable.globalId = "";
        variable.offset = var.offset + field.offset;
        variable.vtype = Variable.VType.Var;
        variable.type = field.type;
        return variable;
    }

    private Variable registerLocalValue(Function func, String name, Object type, boolean isVar)
    {
        func.localArea -= getSizeOfType((Expr) type);
        Variable variable = newLocalVar(name, func.localArea, type);
        variable.isConst = !isVar;
        func.localVars.add(variable);
        return variable;
    }

    private Variable registerReturnValue(Function func, String name, Object type)
    {
        Variable variable = newLocalVar(name, func.argsArea, type);
        int size = getSizeOfType((Expr) type);
        func.argsArea += size;
        func.results.add(variable);
        return variable;
    }

    private Variable registerParam(Function func, String name, Object type)
    {
        Variable variable = newLocalVar(name, func.argsArea, type);
        int size = getSizeOfType((Expr) type);
        func.argsArea += size;
        func.params.add(variable);
        return variable;
    }

    private Variable newLocalVar(String name, int offset, Object type)
    {
        Variable variable = new Variable();
        variable.isConst = false;
        variable.name = name;
        variable.type = type;
        variable.isGlobal = false;
        variable.offset = offset;
        variable.vtype = Variable.VType.Var;
        return variable;
    }

    private Variable newVar(String packageName, String variableName, Object type)
    {
        Variable variable = new Variable();
        variable.packageName = packageName;
        variable.name = variableName;
        variable.type = type;
        variable.globalId = packageName + "." + variableName;
        variable.isGlobal = true;
        variable.isConst = false;
        variable.vtype = Variable.VType.Var;
        return variable;
    }

    private Variable newConst(String packageName, String variableName, Object type)
    {
        Variable variable = new Variable();
        variable.packageName = packageName;
        variable.name = variableName;
        variable.type = type;
        variable.globalId = packageName + "." + variableName;
        variable.isGlobal = true;
        variable.isConst = true;
        return variable;
    }

    public Object getRealType(Expr type)
    {
        switch(type.getClass().getName())
        {
            case "StructExpr": case "ArrayType": case "StarType": case "Ellipsis":
            case "MapType": case "InterfaceType": case "SliceExpr":
                return type;
            case "TInt": case "TBool": case "TString": case "TInterface": case "TNil":
                return type;
            case "Id":
                Id id = (Id)type;
                if(id.object == null)
                    return id;
                switch(((ScopeObject)id.object).name)
                {
                    case "nil": case "int": case "int32": case "string":
                    case "uint8": case "uint16": case "uintptr": case "bool":
                        return type;
                    default:
                        Object obj = ((ScopeObject) id.object).field;

                        if(obj.getClass().getName().equals("TypeDecl"))
                        {
                            return getRealType(((TypeDecl) obj).type);
                        }
                }
            case "SelectorExpr":
                if(exportedIds.containsKey(utils.getQualifiedId((SelectorExpr) type)))
                    return getRealType(exportedIds.get(utils.getQualifiedId((SelectorExpr) type)));
            case "BasicLit":
                BasicLit basicLit = (BasicLit) type;
                if(basicLit.kind == LitKind.DecimalInt || basicLit.kind == LitKind.ImaginaryInt ||
                basicLit.kind == LitKind.BinaryInt || basicLit.kind == LitKind.OctalInt ||
                basicLit.kind == LitKind.HexInt || basicLit.kind == LitKind.RuneInt)
                    return new TInt();
                else if(basicLit.kind == LitKind.String)
                    return new TString();
        }
        return null;
    }

    public String getKind(Expr expr)
    {
        switch(getRealType(expr).getClass().getName())
        {
            case "Id":
                Id id = (Id)expr;
                switch(id.name)
                {
                    case "nil": return "nil";
                    case "int": return "int";
                    case "int32": return "int32";
                    case "string": return "string";
                    case "uint8": return "uint8";
                    case "uint16": return "uint16";
                    case "uintptr": return "uintptr";
                    case "bool": return "bool";
                    default:
                        System.out.println("type error");
                        return "";
                }
            case "StructExpr": return "Struct";
            case "ArrayType": return "Array";
            case "SliceExpr": return "Slice";
            case "StarType": return "Pointer";
            case "Ellipsis": return "Ellipsis";
            case "MapType": return "Map";
            case "InterfaceType":
            case "TInterface":
                return "interface";
            case "TInt": return "int";
            case "TString": return "string";
            case "TBool": return "bool";
            case "TNil": return "nil";
        }
        return "";
    }

    public int getSizeOfType(Expr type)
    {
        switch(getKind( type))
        {
            case "Slice": return 24;
            case "string": return 16;
            case "int": return 8;
            case "Pointer": case "uintptr": case "Map": return 8;
            case "uint8": return 1;
            case "uint16": return 2;
            case "bool": return 8;
            case "interface": return 16;
            case "Array":
                ArrayType arrayType = (ArrayType)getRealType(type);
                assert arrayType != null;
                return getSizeOfType(arrayType.type) * Integer.parseInt(((BasicLit)arrayType.len).value);
            case "Struct":
                return calcStructOffset((StructExpr) Objects.requireNonNull(getRealType(type)));
        }
        return 0;
    }

    public final ScopeObject NullType = new ScopeObject(null, ScopeObject.KIND.Const, "nil");
    public final ScopeObject TrueType = new ScopeObject(null, ScopeObject.KIND.Const, "true");
    public final ScopeObject FalseType = new ScopeObject(null, ScopeObject.KIND.Const, "false");
    public final ScopeObject StringType = new ScopeObject(null, ScopeObject.KIND.Type, "string");
    public final ScopeObject UintptrType = new ScopeObject(null, ScopeObject.KIND.Type, "uintptr");
    public final ScopeObject BoolType = new ScopeObject(null, ScopeObject.KIND.Type, "bool");
    public final ScopeObject IntType = new ScopeObject(null, ScopeObject.KIND.Type, "int");
    public final ScopeObject Uint8Type = new ScopeObject(null, ScopeObject.KIND.Type, "uint8");
    public final ScopeObject Uint16Type = new ScopeObject(null, ScopeObject.KIND.Type, "uint16");
    public final Variable NullVar = new Variable();

    private void setGlobalScope()
    {
        NullVar.name = "nil";
        NullVar.isGlobal = true;
        NullVar.globalId = "nil";
        NullVar.isConst = true;
        NullVar.vtype = Variable.VType.Nil;
        NullVar.type = new TNil();
        globalScope = new Scope();
        globalScope.insert(NullType);
        globalScope.insert(TrueType);
        globalScope.insert(FalseType);
        globalScope.insert(StringType);
        globalScope.insert(UintptrType);
        globalScope.insert(BoolType);
        globalScope.insert(IntType);
        globalScope.insert(Uint8Type);
        globalScope.insert(Uint16Type);
        globalScope.insert(new ScopeObject(null, ScopeObject.KIND.Func, "new"));
        globalScope.insert(new ScopeObject(null, ScopeObject.KIND.Func, "append"));
        globalScope.insert(new ScopeObject(null, ScopeObject.KIND.Func,"len"));
        globalScope.insert(new ScopeObject(null, ScopeObject.KIND.Func, "make"));
        globalScope.insert(new ScopeObject(null, ScopeObject.KIND.Func, "cap"));
        globalScope.insert(new ScopeObject(null, ScopeObject.KIND.Func, "panic"));
        globalScope.insert(new ScopeObject(null, ScopeObject.KIND.Func, "delete"));
    }

    private int calcStructOffset(StructExpr struct)
    {
        int offset = 0;
        for(Field field : struct.fields)
        {
            offsets.put(field, offset);
            field.offset = offset;
            offset += getSizeOfType(field.type);
        }
        struct.size = offset;
        return offset;
    }

    private void registerMethod(Method method)
    {
        Map<String, Method> methods;
        if(recvMethods.containsKey(method.recvName.object))
        {
            methods = recvMethods.get(method.recvName.object);
        }
        else
        {
            methods = new HashMap<>();
            recvMethods.put(method.recvName.object, methods);
        }
        methods.put(method.name, method);
    }

    private void registerString(Expr expr)
    {
        if(expr == null)
            return;
        switch(expr.getClass().getName())
        {
            case "SelectorExpr": registerString(((SelectorExpr)expr).expr);break;
            case "CallExpr":
                CallExpr callExpr = (CallExpr) expr;
                registerString(callExpr.func);
                for(Expr arg : callExpr.args)
                    registerString(arg);
                break;
            case "BasicLit":
                BasicLit basicLit = (BasicLit) expr;
                if(basicLit.kind == LitKind.String)
                {
                    StringContainer container = new StringContainer();
                    int strlen = 0;
                    for(int i=0; i<basicLit.value.length(); i++)
                    {
                        if (basicLit.value.charAt(i) != '\\')
                        {
                            strlen++;
                        }
                    }
                    container.len = strlen - 2;
                    container.label = currentPackage.name + String.valueOf(currentPackage.stringIndex);
                    currentPackage.stringIndex++;
                    container.value = basicLit.value;
                    container.lit = basicLit;
                    currentPackage.addString(container);
                }
                break;
            case "CompositeLit":
                CompositeLit compositeLit = (CompositeLit) expr;
                for(Expr expr1 : compositeLit.value)
                    registerString(expr1);
                break;
            case "UnaryExpr":
                registerString(((UnaryExpr)expr).x);
                break;
            case "Expression":
                Expression expression = (Expression) expr;
                registerString(expression.A);
                registerString(expression.B);
                break;
            case "IndexExpr":
                IndexExpr indexExpr = (IndexExpr) expr;
                registerString(indexExpr.index);
                registerString(indexExpr.x);
                break;
            case "SliceExpr":
                SliceExpr sliceExpr = (SliceExpr) expr;
                for(Expr expr1 : sliceExpr.index)
                    registerString(expr1);
                registerString(sliceExpr.x);
                break;
            case "StarType":
                StarType starType = (StarType) expr;
                registerString(starType.type);
                break;
            case "KeyValueExpr":
                KeyValueExpr keyValueExpr = (KeyValueExpr) expr;
                registerString(keyValueExpr.key);
                registerString(keyValueExpr.value);
                break;
            case "TypeAssertExpr":
                TypeAssertExpr typeAssertExpr = (TypeAssertExpr) expr;
                registerString(typeAssertExpr.x);
                break;

        }
    }

    private Object getReturnType(CallExpr callExpr) throws IllegalAccessException
    {
        List<Expr> ans = new ArrayList<>();
        Expr expr = callExpr.func;
        switch(callExpr.func.getClass().getName())
        {
            case "Id":
                ScopeObject scopeObject = (ScopeObject) ((Id)expr).object;
                switch(scopeObject.kind)
                {
                    case Type:
                        ans.add(expr);
                    case Func:
                        switch (scopeObject.name)
                        {
                            case "len": case "cap":
                                ans.add(new TInt());
                            case "new":
                                StarType starType = new StarType();
                                starType.type = callExpr.args.get(0);
                                ans.add(starType);
                            case "make":
                                ans.add(callExpr.args.get(0));
                            case "append":
                                ans.add(callExpr.args.get(0));
                        }
                        if(scopeObject.field != null && scopeObject.field.getClass().getName().equals("FuncDecl"))
                        {
                            return ((FuncDecl)scopeObject.field).type.results;
                        }
                }
                break;
            case "ArrayType": ans.add(expr);break;
            case "SelectorExpr":
                SelectorExpr selectorExpr = (SelectorExpr) expr;
                if(isType(expr))
                {
                    ans.add(expr);
                }
                else if(utils.isQI(selectorExpr))
                {
                    ForeignFunc foreignFunc = lookupForeignFunc(utils.getQualifiedId(selectorExpr));
                    return foreignFunc.funcDecl.type.results;
                }
                else
                {
                    Object type = getType(selectorExpr.expr);
                    Method method = lookupMethod(type,  selectorExpr.sel);
                    return method.funcType.results;
                }
            case "InterfaceType":
                ans.add(new TInterface());
        }
        return ans;
    }

    private Method lookupMethod(Object type, Id name)
    {
        if(type.getClass().getName().equals("StarType"))
        {
            StarType starType = (StarType) type;
            type = starType.type;
        }

        ScopeObject scopeObject = new ScopeObject();

        switch(type.getClass().getName())
        {
            case "Id":
                Id id = (Id)type;
                scopeObject = (ScopeObject)id.object;
                break;
            case "SelectorExpr":
                SelectorExpr selectorExpr = (SelectorExpr) type;
                if(exportedIds.containsKey(utils.getQualifiedId(selectorExpr)))
                {
                    Id id1 = exportedIds.get(utils.getQualifiedId(selectorExpr));
                    scopeObject = (ScopeObject) id1.object;
                }
        }

        if(recvMethods.containsKey(scopeObject))
        {
            if(recvMethods.get(scopeObject).containsKey(name.name))
                return recvMethods.get(scopeObject).get(name.name);
        }
        return null;
    }

    private ForeignFunc lookupForeignFunc(String id)
    {
        ForeignFunc foreignFunc = new ForeignFunc();
        if(exportedIds.containsKey(id))
        {
            Id foreign = exportedIds.get(id);
            foreignFunc.symbol = id;
            ScopeObject scopeObject = (ScopeObject) foreign.object;
            if(scopeObject.field.getClass().getName().equals("FuncDecl"))
            {
                foreignFunc.funcDecl = (FuncDecl)scopeObject.field;
            }
        }
        return foreignFunc;
    }

    private boolean isType(Expr expr)
    {
        switch(expr.getClass().getName())
        {
            case "ArrayType":
                return true;
            case "Id":
                Id id = (Id)expr;
                return ((ScopeObject)id.object).kind == ScopeObject.KIND.Type;
            case "SelectorExpr":
                SelectorExpr selectorExpr = (SelectorExpr) expr;
                if(selectorExpr.expr != null && ((ScopeObject)((Id)selectorExpr.expr).object).kind == ScopeObject.KIND.Pkg)
                {
                    if(exportedIds.containsKey(utils.getQualifiedId(selectorExpr)))
                    {
                        if(((ScopeObject)exportedIds.get(utils.getQualifiedId(selectorExpr)).object).kind == ScopeObject.KIND.Type)
                            return true;
                    }
                }
                break;
            case "StarExpr":
                StarType starType = (StarType) expr;
                return isType(starType.type);
            case "InterfaceType":
                return true;
        }
        return false;
    }

    public Object getType(Expr expr) throws IllegalAccessException
    {
        switch(expr.getClass().getName())
        {
            case "Id":
                Id id = (Id)expr;
                ScopeObject scopeObject = (ScopeObject)id.object;
                switch(scopeObject.kind)
                {
                    case Var:
                        if(scopeObject.value != null)
                            return ((Variable)scopeObject.value).type;
                        else
                        {
                            switch(scopeObject.field.getClass().getName())
                            {
                                case "ValueDecl":
                                    ValueDecl valueDecl = (ValueDecl) scopeObject.field;
                                    if(valueDecl.isMulti)
                                    {
                                        for(int i=0; i<valueDecl.names.size(); i++)
                                        {
                                            if(valueDecl.names.get(i) == id)
                                                return valueDecl.types.get(i);
                                        }
                                    }
                                    else
                                        return valueDecl.type;
                                    break;
                                case "Field":
                                    Field field = (Field) scopeObject.field;
                                    return field.type;
                                case "AssignStatement":
                                    AssignStatement assignStatement = (AssignStatement) scopeObject.field;
                                    for(int i=0; i<assignStatement.left.size(); i++)
                                    {
                                        for(java.lang.reflect.Field field1 : assignStatement.left.get(i).getClass().getDeclaredFields())
                                        {
                                            if(field1.getType().getName().equals("Id"))
                                            {
                                                Object object = new Object();
                                                object = field1.get(object);
                                                if((Id)object == id)
                                                {
                                                    return assignStatement.right.get(i);
                                                }
                                            }
                                        }
                                    }
                                    break;
                            }
                        }
                    case Const:
                        if(scopeObject.name.equals("true") || scopeObject.name.equals("false"))
                        {
                            return new TBool();
                        }
                        else
                        {
                            if(scopeObject.field.getClass().getName().equals("ValueDecl"))
                            {
                                ValueDecl valueDecl = (ValueDecl) scopeObject.field;
                                if(valueDecl.isMulti)
                                {
                                    for(int i=0; i<valueDecl.names.size(); i++)
                                    {
                                        if(valueDecl.names.get(i) == id)
                                            return valueDecl.types.get(i);
                                    }
                                }
                                else
                                    return valueDecl.type;
                            }
                        }
                }
                break;
            case "BasicLit":
                BasicLit basicLit = (BasicLit) expr;
                if(basicLit.kind == LitKind.String)
                    return new TString();
                else if(basicLit.kind == LitKind.BinaryInt || basicLit.kind == LitKind.RuneInt ||
                basicLit.kind == LitKind.HexInt || basicLit.kind == LitKind.OctalInt ||
                basicLit.kind == LitKind.ImaginaryInt || basicLit.kind == LitKind.DecimalInt)
                    return new TInt();
                break;
            case "UnaryExpr":
                UnaryExpr unaryExpr = (UnaryExpr) expr;
                if(unaryExpr.op == OP.Add || unaryExpr.op == OP.Sub)
                    return getType(unaryExpr.x);
                else if(unaryExpr.op == OP.Not)
                    return new TBool();
                else if(unaryExpr.op == OP.And)
                {
                    Object type = getType(unaryExpr.x);
                    StarType starExpr = new StarType();
                    starExpr.type = (Expr) type;
                    return starExpr;
                }
                break;
            case "Expression":
                Expression expression = (Expression) expr;
                switch (expression.op)
                {
                    case Eql:case Neq:case Lss:case Gtr:case Leq:case Geq:
                        return new TBool();
                    default:return getType(expression.A);
                }
            case "IndexExpr":
                IndexExpr indexExpr = (IndexExpr) expr;
                Object type = getType(indexExpr.x);
                return getElementTypeOfCollectionType(expr);
            case "CallExpr":
                CallExpr callExpr = (CallExpr) expr;
                List<Expr> ans = utils.typeConversion(getReturnType(callExpr), Expr.class);
                return ans.get(0);
            case "SliceExpr":
                SliceExpr sliceExpr = (SliceExpr) expr;
                Object obj = getType(sliceExpr.x);
                if(getKind((Expr) obj).equals("string"))
                    return new TString();
                Expr elementType = null;
                if(obj.getClass().getName().equals("ArrayType"))
                {
                    ArrayType arrayType = (ArrayType)obj;
                    elementType = arrayType.type;
                }
                ArrayType arrayType = new ArrayType();
                arrayType.type = elementType;
                return arrayType;
            case "StarType":
                StarType starType = (StarType)expr;
                Object object = getType(starType.type);
                return object;
            case "SelectorExpr":
                SelectorExpr selectorExpr = (SelectorExpr) expr;
                if(utils.isQI(selectorExpr))
                {
                    if(exportedIds.containsKey(utils.getQualifiedId(selectorExpr)))
                        return getType(exportedIds.get(utils.getQualifiedId(selectorExpr)));
                }
                else
                {
                    Object realType = getRealType((Expr) Objects.requireNonNull(getType(selectorExpr.expr)));
                    StructExpr structExpr = new StructExpr();
                    switch(realType.getClass().getName())
                    {
                        case "StructExpr": structExpr = (StructExpr) realType; break;
                        case "StarType": structExpr = (StructExpr) getRealType(((StarType)realType).type);break;
                    }
                    for(Field field : structExpr.fields)
                    {
                        if(field.id.name == selectorExpr.sel.name)
                            return field;
                    }
                    return realType;
                }
                break;
            case "CompositeLit":
                return expr;
            case "TypeAssertExpr":
                return expr;
        }
        return null;
    }

    private Object getElementTypeOfCollectionType(Expr expr)
    {
        Object realType = getRealType((Expr)expr);
        switch(getKind((Expr)expr))
        {
            case "Slice": case "Array":
            switch (realType.getClass().getName())
            {
                case "ArrayType":
                    return ((ArrayType)realType).type;
                case "Ellipsis":
                    return ((Ellipsis)realType).x;
                case "SliceExpr":
                    return ((SliceExpr)realType).x;
            }
            case "Map":
                assert realType != null;
                return ((MapType)realType).valueType;
        }
        return null;
    }

    private Object getKeyTypeOfCollectionType(Expr expr)
    {
        Object realType = getRealType((Expr)expr);
        switch(getKind((Expr)expr))
        {
            case "Slice": case "Array": case "string":
                return new TInt();
            case "Map":
                assert realType != null;
                return ((MapType)realType).keyType;
        }
        return null;
    }
}
